from typing import Any, Callable

from pymodbus.client import ModbusTcpClient
from HslCommunication import MelsecMcNet, MelsecMcAsciiNet
from utils.MC_protocol import MCNet4C
from core import exceptions
from core.log import logger


def generateMap():
    return {
        "BCFX-MB": {
            "protocol": "modbus协议",
            "driver": BCFXMaster,
            "brand": "北辰",
            "model": "三菱FX"
        },
        "BCQ": {
            "protocol": "modbus协议",
            "driver": BCQMaster,
            "brand": "北辰",
            "model": "三菱Q"
        },
        "MC4E-BIN": {
            "protocol": "4E协议二进制",
            "driver": MCNetBinMaster,
            "brand": "三菱",
            "model": "Q"
        },
        "MC4E-ASCII": {
            "protocol": "4E协议二进制",
            "driver": MCNetASCIIMaster,
            "brand": "三菱",
            "model": "Q"
        },
        "MC4C-ASCII-NET": {
            "protocol": "4C协议ASCII转以太网",
            "driver": MC4CNetASCIIMaster,
            "brand": "三菱",
            "model": "Q"
        }
    }


ReadFuncMap = {
    "Y": 1,
    "M": 1,
    "T": 1,
    "C": 1,
    "S": 1,
    "X": 2,
    "D": 3,
    "W": 3,
    "R": 3
}

BCFXAddrMap: dict[str, Callable] = {
    "Y": lambda addr: addr,
    "M": lambda addr: addr + 10000 if addr < 8000 else addr + 5000,
    "T": lambda addr: addr + 6000,
    "C": lambda addr: addr + 7000,
    "S": lambda addr: addr + 30000,
    "X": lambda addr: addr,
    "D": lambda addr: addr + 2000 if addr < 8000 else addr + 1300,
    "R": lambda addr: addr + 20000,
}

BCQAddrMap: dict[str, Callable] = {
    "Y": lambda addr: addr,
    "M": lambda addr: addr + 16400,
    "X": lambda addr: addr,
    "D": lambda addr: addr,
    "W": lambda addr: addr + 16000,
    "R": lambda addr: addr + 36100,
}


class ProtocolMaster:
    def __init__(self):
        pass

    def connect(self):
        pass

    def disconnect(self):
        pass

    def read(self, func, addr, size):
        pass


class BCMaster(ProtocolMaster):
    def __init__(self, host: str = "127.0.0.1", port: int = 502):
        super().__init__()
        self.host = host
        self.port = int(port)
        self.plc = ModbusTcpClient(host=host, port=port)
        self.funcMap: dict
        self.addrMap: dict

    def connect(self):
        self.plc.connect()

    def disconnect(self):
        self.plc.close()

    def read(self, srcFunc, srcAddr, srcSize):
        func = self.funcMap[srcFunc]
        addr = self.addrMap[srcFunc](srcAddr)
        size = int(srcSize)
        if func == 1:
            opt = self.plc.read_coils(addr, size, slave=1)
            res = [int(bit) for bit in opt.bits]
        elif func == 2:
            opt = self.plc.read_discrete_inputs(addr, size, slave=1)
            res = [int(bit) for bit in opt.bits]
        elif func == 3:
            opt = self.plc.read_holding_registers(addr, size, slave=1)
            res = [int(bit) for bit in opt.registers]
        elif func == 4:
            opt = self.plc.read_input_registers(addr, size, slave=1)
            res = [int(bit) for bit in opt.registers]
        else:
            raise ValueError(f"寄存器类型<{srcFunc}>不正确：")
        if opt.isError():
            raise IOError(f"Modbus 通讯失败：{opt}")
        return res
    

class BCFXMaster(BCMaster):
    def __init__(self, host: str = "127.0.0.1", port: int = 502):
        super().__init__(host=host, port=port)
        self.funcMap = ReadFuncMap
        self.addrMap = BCFXAddrMap
    

class BCQMaster(BCMaster):
    def __init__(self, host: str = "127.0.0.1", port: int = 502):
        super().__init__(host=host, port=port)
        self.funcMap = ReadFuncMap
        self.addrMap = BCQAddrMap


class MCNetMaster(ProtocolMaster):
    def __init__(self, protocol, host: str = "127.0.0.1", port: int = 8000):
        super(MCNetMaster, self).__init__()
        self.host = host
        self.port = port
        self.plc = protocol(ipAddress=self.host, port=self.port)
        self.connected = False
        # self.connect()

    def connect(self):
        if not self.connected:
            opt = self.plc.ConnectServer()
            if opt.IsSuccess:
                self.connected = True
            else:
                raise exceptions.ProtocolError(f"MC4E协议连接失败：{opt.ErrorCode}，请检查设备是否正常连接")

    def disconnect(self):
        opt = self.plc.ConnectClose()
        if opt.IsSuccess:
            self.connected = False
        else:
            raise exceptions.ProtocolError(f"MC4E协议通信端口关闭失败：{opt.ErrorCode}")

    def read(self, func: str, addr: int, size: int):
        self.connect()
        if func.upper() in ["X", "Y", "W"]:
            address = func + hex(addr)[2:]
            opt = self.plc.ReadBool(address, size)
        elif func.upper() in ["M", "L", "F", "V", "B"]:
            address = func + str(addr)
            opt = self.plc.ReadBool(address, size)
        else:
            address = func + str(addr)
            opt = self.plc.ReadUInt16(address, size)
        if opt.IsSuccess:
            return [int(c) for c in opt.Content]


class MCNetBinMaster(MCNetMaster):
    def __init__(self, host: str = "127.0.0.1", port: int = 8000):
        super(MCNetBinMaster, self).__init__(MelsecMcNet, host=host, port=port)


class MCNetASCIIMaster(MCNetMaster):
    def __init__(self, host: str = "127.0.0.1", port: int = 8000):
        super(MCNetASCIIMaster, self).__init__(MelsecMcAsciiNet, host=host, port=port)


class MC4CNetASCIIMaster(ProtocolMaster):
    def __init__(self, host: str, port: int):
        super(MC4CNetASCIIMaster, self).__init__()
        self.host = host
        self.port = port
        self.plc = MCNet4C(host=self.host, port=self.port, timeout=1)

    def connect(self):
        if not self.plc.connected:
            self.plc.connect()

    def disconnect(self):
        self.plc.disconnect()

    def read(self, func, addr, size):
        try:
            self.connect()
            if func.upper() == "X":
                opt = self.plc.GetX(addr, size)
            elif func.upper() == "Y":
                opt = self.plc.GetY(addr, size)
            elif func.upper() == "M":
                opt = self.plc.GetM(addr, size)
            elif func.upper() == "D":
                opt = self.plc.GetD(addr, size)
            elif func.upper() == "L":
                opt = self.plc.GetL(addr, size)
            elif func.upper() == "W":
                opt = self.plc.GetW(addr, size)
            elif func.upper() == "R":
                opt = self.plc.GetR(addr, size)
            elif func.upper() == "F":
                opt = self.plc.GetF(addr, size)
            else:
                return None

        except Exception as e:
            logger.error(str(e))
            self.disconnect()
            return None
        if opt.is_success:
            return opt.data
        else:
            return None


protocolSelections: dict[str, Any] = generateMap()
